#region Copyright & License Information
/*
 * Copyright (c) The OpenRA Developers and Contributors
 * This file is part of OpenRA, which is free software. It is made
 * available to you under the terms of the GNU General Public License
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version. For more
 * information, see COPYING.
 */
#endregion

using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.UpdateRules.Rules;

namespace OpenRA.Mods.Common.UpdateRules
{
	public sealed class UpdatePath
	{
		// Define known update paths from stable tags to the current bleed tip
		//
		// This file should be maintained separately on prep branches vs bleed.
		// The bleed version of this file should ignore the presence of the prep branch
		// and list rules from the playtest that forked the prep branch and current bleed.
		// The prep branch should maintain its own list of rules along the prep branch
		// until the eventual final release.
		//
		// When a final release has been tagged the update paths from the prep branch
		// can be merged back into bleed by replacing the forking-playtest-to-bleed path
		// with the prep playtest-to-playtest-to-release paths and finally a new/modified
		// release-to-bleed path.
		static readonly UpdatePath[] Paths =
		[
			new("release-20230225", "release-20231010",
			[
				new TextNotificationsDisplayWidgetRemoveTime(),
				new RenameEngineerRepair(),
				new ProductionTabsWidgetAddTabButtonCollection(),
				new RemoveTSRefinery(),
				new RenameMcvCrateAction(),
				new RenameContrailWidth(),
				new RemoveExperienceFromInfiltrates(),
				new AddColorPickerValueRange(),

				// Execute these rules last to avoid premature yaml merge crashes.
				new ExplicitSequenceFilenames(),
				new RemoveSequenceHasEmbeddedPalette(),
				new RemoveNegativeSequenceLength(),
			]),

			new("release-20231010", "release-20250303",
			[
				new RemoveValidRelationsFromCapturable(),
				new ExtractResourceStorageFromHarvester(),
				new ReplacePaletteModifiers(),
				new RemoveConyardChronoReturnAnimation(),
				new RemoveEditorSelectionLayerProperties(),
				new AddMarkerLayerOverlay(),
				new AddSupportPowerBlockedCursor(),
				new MovePreviewFacing(),
				new RenameOnDeath(),
				new RemoveParentTopParentLeftSubstitutions(),
				new RenameWidgetSubstitutions(),

				// Execute these rules last to avoid premature yaml merge crashes.
				new ReplaceCloakPalette(),
				new AbstractDocking(),
			]),
			new("release-20250303", "release-20250330", []),
			new("release-20250330", [

				// bleed only changes here.
				new ReplaceBaseAttackNotifier(),
				new RemoveBuildingInfoAllowPlacementOnResources(),
				new EditorMarkerTileLabels(),
				new RemoveBarracksTypesAndVehiclesTypesInBaseBuilderBotModule(),
				new RemoveAlwaysVisible(),

				// Execute these rules last to avoid premature yaml merge crashes.
				new WithDamageOverlayPropertyRename(),
			]),
		];

		public static IReadOnlyCollection<UpdateRule> FromSource(ObjectCreator objectCreator, string source, bool chain = true)
		{
			// Use reflection to identify types
			var namedType = objectCreator.FindType(source);
			if (namedType != null && namedType.IsSubclassOf(typeof(UpdateRule)))
				return [(UpdateRule)objectCreator.CreateBasic(namedType)];

			return Paths.FirstOrDefault(p => p.source == source)?.Rules(chain);
		}

		public static IEnumerable<string> KnownPaths { get { return Paths.Select(p => p.source); } }
		public static IEnumerable<string> KnownRules(ObjectCreator objectCreator)
		{
			return objectCreator.GetTypesImplementing<UpdateRule>().Select(t => t.Name);
		}

		readonly string source;
		readonly string chainToSource;
		readonly UpdateRule[] rules;
		UpdatePath(string source, UpdateRule[] rules)
			: this(source, null, rules) { }

		UpdatePath(string source, string chainToSource, UpdateRule[] rules)
		{
			this.source = source;
			this.rules = rules;
			this.chainToSource = chainToSource;
		}

		IReadOnlyCollection<UpdateRule> Rules(bool chain = true)
		{
			if (chainToSource != null && chain)
			{
				var child = Paths.First(p => p.source == chainToSource);
				return rules.Concat(child.Rules(chain)).ToList();
			}

			return rules;
		}
	}
}
